package chaos.tool;

import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.InputEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.util.concurrent.Callable;
import java.util.prefs.Preferences;

import jmetest.util.JMESwingTest;

import com.jme.bounding.BoundingBox;
import com.jme.image.Texture;
import com.jme.input.InputHandler;
import com.jme.input.action.InputAction;
import com.jme.input.action.InputActionEvent;
import com.jme.math.FastMath;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.renderer.Camera;
import com.jme.renderer.ColorRGBA;
import com.jme.renderer.Renderer;
import com.jme.scene.Geometry;
import com.jme.scene.Node;
import com.jme.scene.Text;
import com.jme.scene.Spatial.CullHint;
import com.jme.scene.Spatial.TextureCombineMode;
import com.jme.scene.shape.Box;
import com.jme.scene.state.CullState;
import com.jme.scene.state.RenderState;
import com.jme.scene.state.TextureState;
import com.jme.scene.state.ZBufferState;
import com.jme.system.DisplaySystem;
import com.jme.system.canvas.JMECanvas;
import com.jme.system.canvas.SimpleCanvasImpl;
import com.jme.util.GameTaskQueue;
import com.jme.util.GameTaskQueueManager;
import com.jme.util.TextureManager;

public class XDCanvas extends SimpleCanvasImpl {
    private static final int GRID_LINES = 51;
    private static final float GRID_SPACING = 100f;
    private Geometry grid;
    
    private Canvas glCanvas;
    private Node root;
    
    private int bgint;
    
    /**
     * The root node of our text.
     */
    protected Node fpsNode;

    /**
     * Displays all the lovely information at the bottom.
     */
    protected Text fps;

    /**
     * This is used to recieve getStatistics calls.
     */
    protected StringBuffer tempBuffer = new StringBuffer();

    /**
     * This is used to display print text.
     */
    protected StringBuffer updateBuffer = new StringBuffer(30);
    
    
    CameraHandler camhand;
    
    public XDCanvas(int width, int height, int color) {
        super(width, height);
        this.root = new Node("root");
        this.bgint = color;
    }
    
    public boolean getGridEnable() {
    	//2.0
    	if(grid.getCullHint() == CullHint.Always) return true;
    	else return false;
    	//if(grid.getCullMode() == SceneElement.CULL_ALWAYS ) return true;
    	//else return false;
    }
    
    public void setGridEnable(boolean flag) {
    	//2.0
    	if(flag) grid.setCullHint(CullHint.Always);
    	else grid.setCullHint(CullHint.Dynamic);
//    	if(flag) grid.setCullMode( SceneElement.CULL_ALWAYS );
//    	else grid.setCullMode( SceneElement.CULL_DYNAMIC );
    }
    
    public Color makeColor(ColorRGBA rgba, boolean useAlpha) {
        return new Color(rgba.r, rgba.g, rgba.b, (useAlpha ? rgba.a : 1f));
    }
    
    public ColorRGBA makeColorRGBA(Color color) {
        return new ColorRGBA(color.getRed() / 255f, color.getGreen() / 255f,
                color.getBlue() / 255f, color.getAlpha() / 255f);
    }
    
    public Canvas getGlCanvas() {
        if (glCanvas == null) {
            glCanvas = (Canvas)DisplaySystem.getDisplaySystem().createCanvas(width, height);
            glCanvas.setMinimumSize(new Dimension(100, 100));
            glCanvas.addComponentListener(new ComponentAdapter() {
                public void componentResized(ComponentEvent ce) {
                    doResize();
                }
            });

            camhand = new CameraHandler();

            glCanvas.addMouseWheelListener(camhand);
            glCanvas.addMouseListener(camhand);
            glCanvas.addMouseMotionListener(camhand);

            ((JMECanvas) glCanvas).setImplementor(this);
            // -----------END OF GL STUFF-------------
            
            Callable<?> exe = new Callable() {
                public Object call() {
                    forceUpdateToSize();
                    return null;
                }
            };
            GameTaskQueueManager.getManager().getQueue(GameTaskQueue.RENDER).enqueue(exe);
        }
        return glCanvas;
    }
    
    public void forceUpdateToSize() {
        glCanvas.setSize(glCanvas.getWidth(), glCanvas.getHeight() + 1);
        glCanvas.setSize(glCanvas.getWidth(), glCanvas.getHeight() - 1);
    }
    
    protected void doResize() {
        resizeCanvas(glCanvas.getWidth(), glCanvas.getHeight());
        Callable<?> exe = new Callable() {
            public Object call() {
                getCamera().setFrustumPerspective(45.0f, (float) glCanvas.getWidth()/ (float) glCanvas.getHeight(), 1, 10000);
                return null;
            }
        };
        GameTaskQueueManager.getManager().getQueue(GameTaskQueue.RENDER).enqueue(exe);
    }
    
    public void simpleSetup() {
        Color bg = new Color(bgint);
        renderer.setBackgroundColor(makeColorRGBA(bg));
        cam.setFrustumPerspective(45.0f, (float) glCanvas.getWidth()
                / (float) glCanvas.getHeight(), 1, 10000);

        Vector3f loc = new Vector3f(0, 850, -850);
        Vector3f left = new Vector3f(1, 0, 0);
        Vector3f up = new Vector3f(0, 0.7071f, 0.7071f);
        Vector3f dir = new Vector3f(0, -0.7071f, 0.7071f);
        cam.setFrame(loc, left, up, dir);

        root = rootNode;

        // Then our font Text object.
        /** This is what will actually have the text at the bottom. */
        fps = Text.createDefaultTextLabel("FPS label");
        //2.0
        //fps.setCullMode(SceneElement.CULL_NEVER);
        //fps.setTextureCombineMode(TextureState.REPLACE);
        fps.setCullHint(CullHint.Never);
        fps.setTextureCombineMode(TextureCombineMode.Replace);

        // Finally, a stand alone node (not attached to root on purpose)
        fpsNode = new Node("FPS node");
        //2.0
//        fpsNode.setRenderState(fps.getRenderState(RenderState.RS_ALPHA));
//        fpsNode.setRenderState(fps.getRenderState(RenderState.RS_TEXTURE));
        fpsNode.setRenderState(fps.getRenderState(RenderState.RS_BLEND));
        fpsNode.setRenderState(fps.getRenderState(RenderState.RS_TEXTURE));
        fpsNode.attachChild(fps);
        //2.0
        //fpsNode.setCullMode(SceneElement.CULL_NEVER);
        fpsNode.setCullHint(CullHint.Never);
        
        //2.0
        //renderer.enableStatistics(true);

        root.attachChild(grid = createGrid());
        grid.updateRenderState();

        fpsNode.updateGeometricState(0, true);
        fpsNode.updateRenderState();
    };

    public void simpleUpdate() {
        updateBuffer.setLength(0);
        updateBuffer.append("FPS: ").append((int) timer.getFrameRate())
                .append(" - ");
        //2.0
        //updateBuffer.append(renderer.getStatistics(tempBuffer));
        /** Send the fps to our fps bar at the bottom. */
        fps.print(updateBuffer);
    }

    @Override
    public void simpleRender() {
        fpsNode.draw(renderer);
        //2.0
        //renderer.clearStatistics();
    }

    private Geometry createGrid() {
        Vector3f[] vertices = new Vector3f[GRID_LINES * 2 * 2];
        float edge = GRID_LINES / 2 * GRID_SPACING;
        for (int ii = 0, idx = 0; ii < GRID_LINES; ii++) {
            float coord = (ii - GRID_LINES / 2) * GRID_SPACING;
            vertices[idx++] = new Vector3f(-edge, 0f, coord);
            vertices[idx++] = new Vector3f(+edge, 0f, coord);
            vertices[idx++] = new Vector3f(coord, 0f, -edge);
            vertices[idx++] = new Vector3f(coord, 0f, +edge);
        }
        Geometry grid = new com.jme.scene.Line("grid", vertices, null,
                null, null);
        //2.0
        //grid.getBatch(0).getDefaultColor().set(ColorRGBA.darkGray);
        grid.getDefaultColor().set(ColorRGBA.darkGray);
        return grid;
    }
    
    class CameraHandler extends MouseAdapter implements MouseMotionListener,MouseWheelListener {
		Point last = new Point(0, 0);
		Vector3f focus = new Vector3f();
		private Vector3f vector = new Vector3f();
		private Quaternion rot = new Quaternion();
		
		public void mouseDragged(final MouseEvent arg0) {
		    Callable<?> exe = new Callable() {
		        public Object call() {
		            int difX = last.x - arg0.getX();
		            int difY = last.y - arg0.getY();
		            int mult = arg0.isShiftDown() ? 10 : 1;
		            last.x = arg0.getX();
		            last.y = arg0.getY();
		
		            int mods = arg0.getModifiers();
		            if ((mods & InputEvent.BUTTON1_MASK) != 0) {
		                rotateCamera(Vector3f.UNIT_Y, difX * 0.0025f);
		                rotateCamera(getRenderer().getCamera().getLeft(),
		                        -difY * 0.0025f);
		            }
		            if ((mods & InputEvent.BUTTON2_MASK) != 0 && difY != 0) {
		                zoomCamera(difY * mult);
		            }
		            if ((mods & InputEvent.BUTTON3_MASK) != 0) {
		                panCamera(-difX, -difY);
		            }
		            return null;
		        }
		    };
		    GameTaskQueueManager.getManager().getQueue(GameTaskQueue.RENDER)
		            .enqueue(exe);
		}
		
		public void mouseMoved(MouseEvent arg0) {
		}
		
		public void mousePressed(MouseEvent arg0) {
		    last.x = arg0.getX();
		    last.y = arg0.getY();
		}
		
		public void mouseWheelMoved(final MouseWheelEvent arg0) {
		    Callable<?> exe = new Callable() {
		        public Object call() {
		            zoomCamera(arg0.getWheelRotation()
		                    * (arg0.isShiftDown() ? -100 : -20));
		            return null;
		        }
		    };
		    GameTaskQueueManager.getManager().getQueue(GameTaskQueue.RENDER)
		            .enqueue(exe);
		}
		
		public void recenterCamera() {
		    Callable<?> exe = new Callable() {
		        public Object call() {
		            Camera cam = getRenderer().getCamera();
		            Vector3f.ZERO.subtract(focus, vector);
		            cam.getLocation().addLocal(vector);
		            focus.addLocal(vector);
		            cam.onFrameChange();
		            return null;
		        }
		    };
		    GameTaskQueueManager.getManager().getQueue(GameTaskQueue.RENDER)
		            .enqueue(exe);
		}
		
		private void rotateCamera(Vector3f axis, float amount) {
		    Camera cam = getRenderer().getCamera();
		    if (axis.equals(cam.getLeft())) {
		        float elevation = -FastMath.asin(cam.getDirection().y);
		        amount = Math.min(Math.max(elevation + amount,
		                -FastMath.HALF_PI), FastMath.HALF_PI)
		                - elevation;
		    }
		    rot.fromAngleAxis(amount, axis);
		    cam.getLocation().subtract(focus, vector);
		    rot.mult(vector, vector);
		    focus.add(vector, cam.getLocation());
		    rot.mult(cam.getLeft(), cam.getLeft());
		    rot.mult(cam.getUp(), cam.getUp());
		    rot.mult(cam.getDirection(), cam.getDirection());
		    cam.normalize();
		    cam.onFrameChange();
		}
		
		private void panCamera(float left, float up) {
		    Camera cam = getRenderer().getCamera();
		    cam.getLeft().mult(left, vector);
		    vector.scaleAdd(up, cam.getUp(), vector);
		    cam.getLocation().addLocal(vector);
		    focus.addLocal(vector);
		    cam.onFrameChange();
		}
		
		private void zoomCamera(float amount) {
		    Camera cam = getRenderer().getCamera();
		    float dist = cam.getLocation().distance(focus);
		    amount = dist - Math.max(0f, dist - amount);
		    cam.getLocation().scaleAdd(amount, cam.getDirection(),
		            cam.getLocation());
		    cam.onFrameChange();
		}
	}
}